package org.kca.graphics;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.font.TextLayout;
import java.awt.image.BufferedImage;
import java.io.IOException;


import static org.lwjgl.opengl.GL11.*;

public class FontPalette
{
    private int cwidth, cheight, twidth, theight;
    private float fontSize;
    private BufferedImage palette;
    private FontMetrics fm;
    private Font font;
    private Texture fontTex;
    private int[] advance, preAdvance, width;
    
    public FontPalette(Font font)
    {
        BufferedImage sample = new BufferedImage(font.getSize() * 2, font.getSize() * 2,
                BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = sample.createGraphics();
        this.font = font;
        advance = new int[16 * 6];
        preAdvance = new int[16 * 6];
        width = new int[16 * 6];
        fm = g.getFontMetrics(font);
        cwidth = 0;
        cheight = fm.getMaxAscent() + fm.getMaxDescent() + 1;
        for (int i = 32; i < 127; i++)
        {
            char q = (char)i;
            advance[i - 32] = fm.charWidth(q);
            cwidth = Math.max(cwidth, advance[i - 32]);
            TextLayout tl = new TextLayout(q + "", font, g.getFontRenderContext());
            Rectangle rect = tl.getPixelBounds(null, 0, 0);
            preAdvance[i - 32] = (int)Math.round(rect.getX());
            width[i - 32] = (int)Math.round(rect.getWidth());
        }
        fontSize = font.getSize2D();
        g.dispose();
        twidth = cwidth * 16;
        theight = cheight * 6;
        createPalette(font);
        fontTex = new TextureLoader().getTexture(palette);
    }
    
    private void createPalette(Font font)
    {
        palette = new BufferedImage(twidth, theight, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = palette.createGraphics();
        g.setBackground(new Color(0, 0, 0, 0));
        g.clearRect(0, 0, twidth, theight);
        g.setColor(Color.white);
        g.setFont(font);
        FontMetrics fm = g.getFontMetrics(font);
        for (int i = 0; i < 6; i++)
        {
            for (int j = 0; j < 16; j++)
            {
                char q = (char)(i * 16 + 32 + j);
                g.drawString(String.valueOf(q), cwidth * j, cheight * i + fm.getMaxAscent());
            }
        }
        g.dispose();
    }
    
    /**
     * Draws a string to the screen at the specified position. The position is specified as 
     * the top left corner of a bounding box which will contain the text. The actual text
     * position within this bounding box will depend on the text to be drawn.
     * @param text the string to draw
     * @param x the x coordinate of the top left corner
     * @param y the y coordinate of the top left corner
     */
    public void drawString(String text, int x, int y)
    {
        int xpos = x;
        
        fontTex.bind();
        for (int i = 0; i < text.length(); i++)
        {
            int q = (int)text.charAt(i);
            if (q < 32 || q > 127) continue;
            int row = (q - 32) / 16;
            int col = q % 16;
            float tx = col / 16.0f * fontTex.getWidth();
            float ty = row / 6.0f * fontTex.getHeight();
            glBegin(GL_QUADS);
            {
                glTexCoord2f(tx + preAdvance[q - 32] * fontTex.getWidth() / (float)twidth, ty);
                glVertex2f(xpos + preAdvance[q - 32], y);

                glTexCoord2f(tx + (preAdvance[q - 32] * fontTex.getWidth()) / (float)twidth, 
                        ty + fontTex.getHeight() / 6.0f);
                glVertex2f(xpos + preAdvance[q - 32], y + cheight);

                glTexCoord2f(tx + (preAdvance[q - 32] + width[q - 32]) * fontTex.getWidth() / 
                        (float)twidth, ty + fontTex.getHeight() / 6.0f);
                glVertex2f(xpos + preAdvance[q - 32] + width[q - 32], y + cheight);

                glTexCoord2f(tx + (preAdvance[q - 32] + width[q - 32]) * fontTex.getWidth() / 
                        (float)twidth, ty);
                glVertex2f(xpos + preAdvance[q - 32] + width[q - 32], y);
            }
            glEnd();
            xpos += advance[q - 32];
        }
    }
    
    /**
     * Draws a clipped part of a string to the screen at the specified position. 
     * The position is specified as the top left corner of a bounding box which will 
     * contain the text. The entire bounding box will be clipped so that only that which is
     * displayed within the specified internal box will be displayed. 
     * @param text the string to draw
     * @param x the x coordinate of the top left corner of the outer bounding box
     * @param y the y coordinate of the top left corner of the outer bounding box
     * @param bx the x coordinate of the top left corner of the internal clipping bounding box
     * @param by the y coordinate of the top left corner of the internal clipping bounding box
     * @param bw the width of the internal clipping bounding box
     * @param bh the height of the internal clipping bounding box
     */
    public void drawPartialString(String text, int x, int y, int bx, int by, int bw, int bh)
    {
        int xpos = x;
        
        fontTex.bind();
        for (int i = 0; i < text.length(); i++)
        {
            int q = (int)text.charAt(i);
            if (q < 32 || q > 127) continue;
            int row = (q - 32) / 16;
            int col = q % 16;
            float tx = col / 16.0f * fontTex.getWidth();
            float ty = row / 6.0f * fontTex.getHeight();
            int basex = xpos + preAdvance[q - 32];
            int clipx = Math.max(bx, basex);
            int clipy = Math.max(by, y);
            int endx = Math.min(bx + bw, basex + width[q - 32]);
            int endy = Math.min(by + bh, y + cheight);
            if (endx <= clipx || endy <= clipy)
            {
                xpos += advance[q - 32];
                continue;
            }
            glBegin(GL_QUADS);
            {
                glTexCoord2f(tx + (preAdvance[q - 32] + clipx - basex) * fontTex.getWidth() /
                        (float)twidth, ty + (clipy - y) * fontTex.getHeight() / (float)theight);
                glVertex2f(clipx, clipy);

                glTexCoord2f(tx + (preAdvance[q - 32] + clipx - basex) * fontTex.getWidth() / 
                        (float)twidth, ty + (endy - y) * fontTex.getHeight() / (float)theight);
                glVertex2f(clipx, endy);

                glTexCoord2f(tx + (preAdvance[q - 32] + endx - basex) * fontTex.getWidth() / 
                        (float)twidth, ty + (endy - y) * fontTex.getHeight() / (float)theight);
                glVertex2f(endx, endy);

                glTexCoord2f(tx + (preAdvance[q - 32] + endx - basex) * fontTex.getWidth() / 
                        (float)twidth, ty + (clipy - y) * fontTex.getHeight() / (float)theight);
                glVertex2f(endx, clipy);
            }
            glEnd();
            xpos += advance[q - 32];
        }
    }
    
    /**
     * Gets the AWT font backing this font palette
     * @return the AWT font
     */
    public Font getFont()
    {
        return font;
    }
    
    /**
     * Calculates the string width for this string as it will be rendered, which
     * is equal to the sum of the advances of all characters in the string. Note
     * that only ASCII characters between 32 and 126 will be rendered.
     * @param s the string to render
     * @return the string width in pixels
     */
    public int stringWidth(String s)
    {
        int sum = 0;
        for (int i = 0; i < s.length(); i++)
        {
            int c = (int)s.charAt(i);
            sum += advance[c - 32];
        }
        return sum;
    }
    
    /**
     * Gets the width of the specified character. Only valid for characters between ASCII
     * values 32 and 127
     * @param c the width of the specified character
     * @return the width, or 0 if an invalid character is specified
     */
    public int charWidth(char c)
    {
        if (c < 32 || c >= 127)
        {
            return 0;
        }
        return advance[c];
    }
    
    /**
     * Returns the height of this font palette in pixels. This generally represents the
     * maximum height of any character in the font
     * @return the height
     */
    public int getHeight()
    {
        return cheight;
    }
}
